/*
 * @ : Copyright (c) 2021 Phytium Information Technology, Inc. 
 *  
 * SPDX-License-Identifier: Apache-2.0.
 * 
 * @Date: 2021-09-15 19:45:23
 * @LastEditTime: 2021-10-08 17:09:09
 * @Description:  This files is for 
 * 
 * @Modify History: 
 *  Ver   Who        Date         Changes
 * ----- ------     --------    --------------------------------------
 */
#include "ft_assert.h"
#include "parameters.h"
#include "fsleep.h"
#include "f_sdio.h"
#include "f_sdio_hw.h"

void FSdioEnableClk(FSdioCtrl *ctrl_p)
{
    FT_ASSERTVOID(ctrl_p);
    FSDIO_SET_BITS(ctrl_p, FSDIO_REG_CLKENA_OFFSET, FSDIO_CLKENA_CCLK_ENABLE);
}

void FSdioDisableClk(FSdioCtrl *ctrl_p)
{
    FT_ASSERTVOID(ctrl_p);
    FSDIO_CLR_BITS(ctrl_p, FSDIO_REG_CLKENA_OFFSET, FSDIO_CLKENA_CCLK_ENABLE);
}

// void FSdioCardPowerOn(FSdioCtrl *ctrl_p)
// {
//     FT_ASSERTVOID(ctrl_p);
//     FSDIO_SET_BITS(ctrl_p, FSDIO_REG_PWREN_OFFSET, FSDIO_PWREN_ENABLE);
// }

void FSdioCardPowerOff(FSdioCtrl *ctrl_p)
{
    FT_ASSERTVOID(ctrl_p);
    FSDIO_CLR_BITS(ctrl_p, FSDIO_REG_PWREN_OFFSET, FSDIO_PWREN_ENABLE);
}

u32 FSdioSetCardClk(FSdioCtrl *ctrl_p, u32 input_clk_hz)
{
    FT_ASSERTNONERETURN(ctrl_p);
    u32 reg_val;
    u32 first_uhs_div;
    u32 div, drv, sample;

    FSDIO_INFO("will change clock, clk_rate: %ld, input clk: %d", 
              FSDIO_CLK_RATE_HZ, input_clk_hz);

    FSdioDisableClk(ctrl_p); /* disable clk */

    if (FSDIO_25_MHZ <= input_clk_hz)
    {
        reg_val = FSDIO_UHS_REG(0x1, 0x0, 0x4) | FSDIO_UHS_EXT_CLK_ENA;
    }
    else if (FSDIO_400_KHZ == input_clk_hz)
    {
        reg_val = FSDIO_UHS_REG(0x0, 0x1, 0x5) | FSDIO_UHS_EXT_CLK_ENA;
    }
    else
    {
        reg_val = FSDIO_UHS_REG(0x1, 0x0, 0x5) | FSDIO_UHS_EXT_CLK_ENA;
    }

    FSDIO_WRITE_REG(ctrl_p, FSDIO_REG_UHS_REG_EXT_OFFSET, reg_val);

    /* set clk divider */
    first_uhs_div = ((FSDIO_UHS_CLK_DIV_MASK & reg_val) >> 8) + 1;
    div = FSDIO_CLK_RATE_HZ / (2 * first_uhs_div * input_clk_hz);

    if ((2 < div) && (FSDIO_25_MHZ < input_clk_hz))
    {
        drv = div / 3;
        sample = div / 2;
    }
    else if (FSDIO_25_MHZ == input_clk_hz)
    {
        drv = 0;
        sample = div / 2;
    }
    else if (FSDIO_400_KHZ == input_clk_hz)
    {
        drv = div / 2;
        sample = 200;
    }
    else
    {
        sample = div / 2;
        drv = sample;
    }

    reg_val = FSDIO_CLK_DIV(sample, drv, div);
    FSDIO_WRITE_REG(ctrl_p, FSDIO_REG_CLKDIV_OFFSET, reg_val);

    FSDIO_INFO("UHS_REG_EXT ext: %x, CLKDIV: %x", 
              FSDIO_READ_REG(ctrl_p, FSDIO_REG_UHS_REG_EXT_OFFSET),
              FSDIO_READ_REG(ctrl_p, FSDIO_REG_CLKDIV_OFFSET));

    FSdioEnableClk(ctrl_p); /* enable clk */

    return FSDIO_SUCCESS;
}

void FSdioSetBusWidth(FSdioCtrl *ctrl_p, u32 width)
{
    FT_ASSERTVOID(ctrl_p);
    u32 reg_val;

    switch (width)
    {
        case FSDIO_BUS_WIDTH_1:
            reg_val = FSDIO_BUS_1BITS;
            break;
        case FSDIO_BUS_WIDTH_4:
            reg_val = FSDIO_BUS_4BITS;
            break;
        case FSDIO_BUS_WIDTH_8:
            reg_val = FSDIO_BUS_8BITS;
            break;
        default:
            reg_val = FSDIO_BUS_4BITS;
            break;
    }

    FSDIO_WRITE_REG(ctrl_p, FSDIO_REG_CTYPE_OFFSET, reg_val);
    FSDIO_INFO("bus width = %d", width);
    return;
}

u32 FSdioGetBusWidth(FSdioCtrl *ctrl_p)
{
    FT_ASSERTNONERETURN(ctrl_p);
    u32 reg_val = FSDIO_READ_REG(ctrl_p, FSDIO_REG_CTYPE_OFFSET);
    u32 bus_width = FSDIO_BUS_WIDTH_1;

    if (FSDIO_BUS_8BITS & reg_val)
    {
        bus_width = FSDIO_BUS_WIDTH_8;
    }
    else if (FSDIO_BUS_4BITS & reg_val)
    {
        bus_width = FSDIO_BUS_WIDTH_4;
    }

    return bus_width;
}

void FSdioSetDdrMode(FSdioCtrl *ctrl_p, boolean enable)
{
    FT_ASSERTNONERETURN(ctrl_p);

    if (enable)
    {
        FSDIO_SET_BITS(ctrl_p, FSDIO_REG_UHS_REG_OFFSET, FSDIO_UHS_REG_DDR);
        FSDIO_SET_BITS(ctrl_p, FSDIO_REG_EMMC_DDR_REG_OFFSET, FSDIO_EMMC_DDR_CYCLE);
    }
    else
    {
        FSDIO_CLR_BITS(ctrl_p, FSDIO_REG_UHS_REG_OFFSET, FSDIO_UHS_REG_DDR);
        FSDIO_CLR_BITS(ctrl_p, FSDIO_REG_EMMC_DDR_REG_OFFSET, FSDIO_EMMC_DDR_CYCLE);
    }

    FSDIO_INFO("ddr mode: %d", enable);
    return;
}

boolean FSdioCardExists(FSdioCtrl *ctrl_p)
{
    FT_ASSERTZERONUM(ctrl_p);
    u32 status = FSDIO_READ_REG(ctrl_p, FSDIO_REG_CARD_DETECT_OFFSET);
    return (FSDIO_CARD_DETECTED != (FSDIO_CARD_DETECTED & status));
}

boolean FSdioCardWriteProtected(FSdioCtrl *ctrl_p)
{
    FT_ASSERTZERONUM(ctrl_p);
    u32 status = FSDIO_READ_REG(ctrl_p, FSDIO_REG_CARD_WRTPRT_OFFSET);
    return (FSDIO_CARD_WRITE_PROTECTED == (FSDIO_CARD_WRITE_PROTECTED & status));
}

u32 FSdioCtrlReset(FSdioCtrl *ctrl_p, u32 reset_bits)
{
    FT_ASSERTZERONUM(ctrl_p);
    u32 ctrl = FSDIO_READ_REG(ctrl_p, FSDIO_REG_CNTRL_OFFSET);
    u32 retries = 0;
    u32 all_reset_bits = FSDIO_CNTRL_DMA_RESET | FSDIO_CNTRL_FIFO_RESET | FSDIO_CNTRL_CONTROLLER_RESET;

    if ((reset_bits | all_reset_bits) != all_reset_bits)
    {
        FSDIO_ERROR("input parameters error!!");
        return FSDIO_ERR_INVALID_PARA;
    }

    /* Set reset bits */
    ctrl |= reset_bits;
    FSDIO_WRITE_REG(ctrl_p, FSDIO_REG_CNTRL_OFFSET, ctrl);

    /* Wait for the reset bits to be cleared by hardware */
    do
    {
        ctrl = FSDIO_READ_REG(ctrl_p, FSDIO_REG_CNTRL_OFFSET);
        fsleep_microsec(FSDIO_DELAY_US);
    } while ((ctrl & reset_bits) && (FSDIO_TIMEOUT > retries++));

    if (FSDIO_TIMEOUT <= retries)
    {
        FSDIO_ERROR("reset sdio ctrl timeout!!");
        return FSDIO_ERR_TIMEOUT;
    }
    else
    {
        return FSdioSendPrivateCmd(ctrl_p, FSDIO_CMD_UPD_CLK, 0); /* 不发送命令，只更新时钟REG */
    }    
}

/* Software Reset. When set, the DMA Controller resets all its internal registers.
SWR is read/write. It is automatically cleared after 1 clock cycle. */
u32 FSdioBusModeSwrReset(FSdioCtrl *ctrl_p)
{
    FT_ASSERTZERONUM(ctrl_p);
    u32 bus_mode = FSDIO_READ_REG(ctrl_p, FSDIO_REG_BUS_MODE_OFFSET);
    u32 retries = 0;

    /* reset internal dma */
    bus_mode |= FSDIO_BUS_MODE_SWR;
    FSDIO_WRITE_REG(ctrl_p, FSDIO_REG_BUS_MODE_OFFSET, bus_mode);

    do
    {
        bus_mode = FSDIO_READ_REG(ctrl_p, FSDIO_REG_BUS_MODE_OFFSET);
        fsleep_microsec(FSDIO_DELAY_US);
    } while ((bus_mode & FSDIO_BUS_MODE_SWR) && (FSDIO_TIMEOUT > retries++));

    if (FSDIO_TIMEOUT <= retries)
    {
        FSDIO_ERROR("reset internal dma timeout!!");
        return FSDIO_ERR_TIMEOUT;
    }
    else
    {
        return FSDIO_SUCCESS;
    }
}

u32 FSdioSendPrivateCmd(FSdioCtrl *ctrl_p, u32 cmd, u32 arg)
{
    FT_ASSERTZERONUM(ctrl_p);
    u32 reg_val = 0;
    u32 retries = 0;

    FSDIO_INFO("send cmd 0x%x with arg 0x%x", cmd, arg);

    /* write cmd argument */
    FSDIO_WRITE_REG(ctrl_p, FSDIO_REG_CMDARG_OFFSET, arg);

    /* wait until card not busy */
    do
    {
        reg_val = FSDIO_READ_REG(ctrl_p, FSDIO_REG_STATUS_OFFSET);
        fsleep_microsec(FSDIO_DELAY_US);
    } while ((FSDIO_STATUS_CARD_BUSY & reg_val) && (FSDIO_TIMEOUT > retries++));

    if (FSDIO_TIMEOUT <= retries)
    {
        FSDIO_ERROR("send cmd arg timeout!!");
        return FSDIO_ERR_TIMEOUT;
    }

    FSDIO_INFO("card not busy");

    /* send cmd */
    FSDIO_WRITE_REG(ctrl_p, FSDIO_REG_CMD_OFFSET, FSDIO_CMD_START | cmd);

    /* wait cmd sending finished */
    retries = 0;
    do
    {
        reg_val = FSDIO_READ_REG(ctrl_p, FSDIO_REG_CMD_OFFSET);
        FSDIO_INFO("cmd status: 0x%x", reg_val);
        fsleep_microsec(FSDIO_DELAY_US);
    } while ((FSDIO_CMD_START & reg_val) && (FSDIO_TIMEOUT > retries++));

    if (FSDIO_TIMEOUT <= retries)
    {
        FSDIO_ERROR("send cmd timeout!!");
        return FSDIO_ERR_TIMEOUT;
    }

    FSDIO_INFO("cmd send done");

    return FSDIO_SUCCESS;
}
